﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using EMK.Cartography;
using Microsoft.Xna.Framework;
using EMK.LightGeometry;
using System.ComponentModel;

namespace GameLib {
    public class GraphBoard {
        // ---------------------------------------------
        //
        //      Respecter l'ordre du fichier XML!!!
        //
        // ---------------------------------------------
        [ContentSerializerIgnore]
        public Texture2D Background;

        protected string _textureAsset;
        protected INode[] chemin;

        /// <summary>
        /// Chemin d'accès à la texture du plateau à afficher
        /// </summary>
        public string TextureAsset {
            get { return _textureAsset; }
            set { _textureAsset = value; }
        }

        /// <summary>
        /// Nom du plateau
        /// </summary>
        public string Name;
        /// <summary>
        /// Description du plateau
        /// </summary>
        public string Description;
        /// <summary>
        /// Chemin du fichier contenant le graph du plateau
        /// </summary>
        public string GraphFile;

        #region PathFinding Fields
        [ContentSerializerIgnore]
        public Graph G = new Graph();

        [ContentSerializerIgnore]
        [Description("Moteur de pathfinding")]
        public AStar AE;


        [ContentSerializerIgnore]
        [Description("Collection de Node qui correspondent à chaque case du plateau traversée par le chemin.")]
        public INode[] Chemin {
            get { return chemin; }
            set { chemin = (INode[])value; }
        }

        [ContentSerializerIgnore]
        [Description("Indique si un chemin entre un point A et B a été trouvé.")]
        public bool PathFound {
            get { return (chemin == null) ? false : true; }
        }

        /// <summary>
        /// Représente la case de départ ou null
        /// </summary>
        [ContentSerializerIgnore]
        public INode NDepart;

        /// <summary>
        /// Représente la case d'arrivée ou null
        /// </summary>
        [ContentSerializerIgnore]
        public INode NArrivee;

        /// <summary>
        /// Représente la case sélectionnée ou null
        /// </summary>
        [ContentSerializerIgnore]
        public INode Selected;

        #endregion


        /// <summary>
        /// Effacer les points de départ, arrivée, selected et le chemin
        /// </summary>
        public void Clear() {
            NArrivee = null;
            NDepart = null;
            Selected = null;
            chemin = null;
        }


        /// <summary>
        /// Chargement du graph qui represente chaque case du plateau et leur liens.
        /// C'est ce graph qui permet de savoir s'il est possible de se rendre d'un
        /// point A à un point B
        /// </summary>
        public virtual void LoadGraph() {
            G.Clear();

            #region conversion d'un graph standard en un graphPF
            // lecture du fichier .bin qui contient les PF
            // Comme pour le moment, il s'agit de "Node" standard et non de "THNode" (node modifiée pour TH)
            // il faut convertir les "Node" en "THNode" et re-générer les "Arcs" (liens entre les cases)
            Stream streamRead = File.Open(AppDomain.CurrentDomain.BaseDirectory + GraphFile, FileMode.Open);
            BinaryFormatter binaryRead = new BinaryFormatter();

            G = (Graph)binaryRead.Deserialize(streamRead);

            //            // Génération des "Nodes"
            //            foreach (INode node in graph.Nodes) {
            //#warning il va falloir refaire les paths
            //                //TannhauserNode node = (TannhauserNode)n;
            //                //THNode NewNode = new THNode(node.X, node.Y, node.Z, string.Empty);
            //                //NewNode.TypeCase = node.TypeCase;
            //                //NewNode.Passable = node.Passable;
            //                //NewNode.ID = node.ID;
            //                G.AddNode(NewNode);
            //            }
            //            // Génération des "Arcs"
            //            foreach (Arc arc in graph.Arcs) {
            //                int i = G.Nodes.IndexOf(arc.StartNode);
            //                int j = G.Nodes.IndexOf(arc.EndNode);
            //                G.AddArc((THNode)G.Nodes[i], (THNode)G.Nodes[j], 1);
            //            }

            streamRead.Close();
            #endregion

            // une fois G défini, initialiser le moteur de recherche AStar
            AE = new AStar(G);
        }
        public virtual void Load(MyContentManager content) {
            // pour économiser la mémoire, on décharge la carte précédente...
            if (Background != null)
                content.Unload(Background.Name);

            Background = content.Load<Texture2D>(TextureAsset);
            Background.Name = TextureAsset;
            LoadGraph();
        }


        public bool IsAdjacent(INode depart, INode arrivee) {
            bool result = false;
            foreach (INode node in depart.AccessibleNodes) {
                if (node.Equals(arrivee)) {
                    result = true;
                    break;
                }
            }
            return result;
        }

        /// <summary>
        /// Calcul la distance entre un point A et un point B
        /// sans que les cases occupées par des personnages soient considérées comme
        /// des obstacles.
        /// </summary>
        /// <returns>Retourne la distance entre A et B ou -1 si connexion impossible</returns>
        public int Distance(INode pDepart, INode pArrivee) {
            int distance;
            double cost;

            // chercher le chemin le plus court entre A et B afin de calculer
            // la distance
            bool found = AE.SearchPath(pDepart, pArrivee);
            if (found) {
                AE.ResultInformation(out distance, out cost);
            }
            else {
                distance = -1;
            }

            return distance;
        }

        /// <summary>
        /// retourne la seule case accessible en fonction du nombre de points
        /// de mouvements restants au personnage actif
        /// </summary>
        /// <returns>Une case valide ou null si aucune case n'est valide</returns>
        public INode DefinirLeChemin(int maxMoves) {
            // on va mettre dans cette collections tous les noeuds valides
            List<Node> ValideNodes = new List<Node>();
            foreach (Node N in chemin) {
                // Ajouter la case à la collection
                ValideNodes.Add(N);
            }

            if (ValideNodes.Count > 0) {
                // chemin ne contient maintenant que les noeuds valides
                // et le point d'arrivée correspond toujours à la dernière
                // case du parcours
                INode node = (INode)ValideNodes[ValideNodes.Count - 1];
                return node;
            }
            else {
                // on indique qu'aucun parcours n'a été trouvé car aucune
                // case du parcours n'est valides pour un déplacement
                return null;
            }
        }

        /// <summary>
        /// Retourner la case du parcours réellement accessible en fonction
        /// du nombre de points de mouvement restant du personnage.
        /// Si null est retournée, cela signifie qu'aucune case n'est valide!
        /// </summary>
        public virtual INode GetMostAccessiblePF(int maxMoves, out int cost) {
            int index = chemin.Length - 1;
            cost = index;
            INode PF = (INode)chemin[index];

            // TODO : a voir si une méthode "clean" ne serait plas meilleure
            // dans tous les cas, chemin doit être initialiser à null, afin
            // de ne plus afficher le parcours
            chemin = null;

            return PF;
        }

        /// <summary>
        /// Convertir les coordonnées plateau en coordonnées écran
        /// </summary>
        public Vector2 BoardCoordinateToScreenCoordinate(INode node, Camera2D camera) {
            Vector2 position = node.Location;
            return BoardCoordinateToScreenCoordinate(position, camera);
        }
        /// <summary>
        /// Convertir les coordonnées plateau en coordonnées écran
        /// </summary>
        public Vector2 BoardCoordinateToScreenCoordinate(Vector2 position, Camera2D camera) {
            position /= camera.Scale;
            position = Vector2.Transform(position, camera.Transform);
            return position;
        }
        /// <summary>
        /// Convertir les coordonnées écran en coordonnées relatives au plateau.
        /// </summary>
        public INode NoeudSelonCameraPosition(int X, int Y, Camera2D camera) {
            INode NodeMin = null;
            double DistanceMin = -1;
            Point3D P = new Point3D(X, Y, 0);
            foreach (INode N in G.Nodes) {
                Vector2 v2 = new Vector2((float)N.Position.X, (float)N.Position.Y);

                // Il faut multiplier les coordonnées de la position de la souris
                // par l'échelle appliquée
                v2 *= camera.Scale;

                // puis une transformation à partir de la matrice de la caméra
                // sur la position du pion, on retrouve les coordonnées écran
                v2 = Vector2.Transform(v2, camera.Transform);

                Point3D point = new Point3D(v2.X, v2.Y, 0);
                double DistanceTemp = Point3D.DistanceBetween(point, P);
                if (DistanceMin == -1 || DistanceMin > DistanceTemp) {
                    DistanceMin = DistanceTemp;
                    NodeMin = N;
                }
            }
            //Distance = DistanceMin;
            return (INode)NodeMin;
        }

        /// <summary>
        /// Retourner le noeud à la position donnée à condition que la case soit libre
        /// </summary>
        public INode NoeudSelonPosition(Vector2 position) {
            return NoeudSelonPosition((int)position.X, (int)position.Y, true);
        }
        /// <summary>
        /// Retourner le noeud à la position donnée en tenant compte de son accessibilité
        /// </summary>
        public INode NoeudSelonPosition(Vector2 position, bool passable) {
            return NoeudSelonPosition((int)position.X, (int)position.Y, passable);
        }
        /// <summary>
        /// Retourner le noeud à la position donnée en tenant compte de son accessibilité
        /// </summary>
        public INode NoeudSelonPosition(int X, int Y, bool passable) {
            double distance;
            return G.ClosestNode(X, Y, 0, out distance, passable);
        }

    }
}
